/*
 * JWT认证过滤器
 * 位于包：org.example.lethear.security
 * 
 * 这是整个认证系统的核心组件，负责：
 * 1. 拦截每个HTTP请求
 * 2. 从请求头中提取JWT令牌
 * 3. 验证令牌有效性
 * 4. 将用户认证信息设置到Spring Security的SecurityContext中
 * 5. 决定哪些请求需要跳过认证
 */
package org.example.lethear.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

import org.example.lethear.utils.JwtUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * JWT认证过滤器
 * 继承OncePerRequestFilter确保在每个请求中只执行一次
 * 
 * 工作流程：
 * 1. 检查是否需要跳过认证（如登录、注册等公开接口）
 * 2. 从请求头中提取JWT令牌
 * 3. 验证令牌有效性
 * 4. 根据令牌信息加载用户详情
 * 5. 创建认证对象并设置到SecurityContext
 * 6. 继续执行后续过滤器
 */
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    // 注入JWT工具类，用于令牌的验证和解析
    private final JwtUtils jwtUtils;
    
    // 注入用户详情服务，用于根据用户名加载用户信息
    private final UserDetailsServiceImpl userDetailsService;
    
    /**
     * 核心过滤方法，处理每个HTTP请求
     * 
     * @param request HTTP请求对象
     * @param response HTTP响应对象
     * @param filterChain 过滤器链，用于继续处理请求
     * @throws ServletException Servlet异常
     * @throws IOException IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) throws ServletException, IOException {
        
        try {
            // 1. 从请求中提取JWT令牌
            String jwt = getJwtFromRequest(request);
            
            // 2. 检查令牌是否存在且有效
            if (StringUtils.hasText(jwt) && jwtUtils.validateToken(jwt)) {
                
                // 3. 从令牌中提取用户名
                String username = jwtUtils.getUsernameFromToken(jwt);
                
                // 4. 只有当当前安全上下文中没有认证信息时才设置
                // 这避免了重复设置认证信息
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    
                    // 5. 根据用户名加载用户详情
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    
                    // 6. 再次验证令牌（包含用户详情的验证）
                    if (jwtUtils.validateToken(jwt, userDetails)) {
                        
                        // 7. 创建认证对象
                        // UsernamePasswordAuthenticationToken是Spring Security的标准认证对象
                        UsernamePasswordAuthenticationToken authentication = 
                            new UsernamePasswordAuthenticationToken(
                                userDetails,    // 用户详情作为principal
                                null,           // 密码为null，因为JWT认证不需要密码
                                userDetails.getAuthorities()  // 用户权限列表
                            );
                        
                        // 8. 设置认证详情（包含IP地址、会话ID等）
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        
                        // 9. 将认证对象设置到Spring Security的安全上下文中
                        // 这样后续的代码就能获取到当前用户的认证信息
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                        
                        // 10. 记录认证成功的日志
                        System.out.println("设置用户认证信息到安全上下文: " + username);
                    }
                }
            }
        } catch (Exception e) {
            // 11. 如果认证过程中发生异常，记录错误日志
            // 注意：这里只记录日志，不抛出异常，因为认证失败应该继续处理请求
            // 让Spring Security的其他组件处理未认证的情况
            System.err.println("无法设置用户认证信息到安全上下文: " + e.getMessage());
        }
        
        // 12. 继续执行过滤器链中的下一个过滤器
        // 无论认证成功与否，都要继续处理请求
        filterChain.doFilter(request, response);
    }
    
    /**
     * 从HTTP请求中提取JWT令牌
     * 
     * JWT令牌通常放在Authorization请求头中，格式为：
     * Authorization: Bearer <token>
     * 
     * @param request HTTP请求对象
     * @return JWT令牌字符串，如果没有找到则返回null
     */
    private String getJwtFromRequest(HttpServletRequest request) {
        // 1. 获取Authorization请求头的值
        String bearerToken = request.getHeader("Authorization");
        
        // 2. 检查请求头是否存在且以"Bearer "开头
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            // 3. 提取"Bearer "后面的令牌部分
            // substring(7)是因为"Bearer "的长度为7
            return bearerToken.substring(7);
        }
        
        // 4. 如果没有找到有效的令牌，返回null
        return null;
    }
    
    /**
     * 决定是否应该跳过此过滤器
     * 
     * 对于某些公开的API（如登录、注册、健康检查等），
     * 不需要进行JWT认证，可以直接跳过此过滤器
     * 
     * @param request HTTP请求对象
     * @return true表示跳过过滤器，false表示需要执行过滤器
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        // 1. 获取请求的servlet路径
        String path = request.getServletPath();
        
        // 2. 输出调试信息，方便排查问题
        System.out.println("JWT Filter - 请求路径: " + request.getRequestURI() + ", Servlet路径: " + path);
        
        // 3. 定义需要跳过JWT认证的路径
        // 注意：由于应用设置了context-path为/api，所以servlet路径不包含/api前缀
        boolean shouldSkip = path.startsWith("/auth/login") ||        // 登录接口
               path.startsWith("/auth/register") ||                    // 注册接口
               path.startsWith("/auth/refresh") ||                     // 刷新令牌接口
               path.startsWith("/auth/check-") ||                      // 检查可用性接口
               path.startsWith("/public/") ||                          // 所有公开接口
               path.equals("/health") ||                               // 健康检查接口
               path.startsWith("/actuator/") ||                        // Spring Boot监控接口
               path.startsWith("/swagger") ||                          // Swagger文档接口
               path.startsWith("/v3/api-docs") ||                      // OpenAPI文档接口
               path.startsWith("/swagger-ui") ||                       // Swagger UI接口
               path.equals("/error") ||                                // 错误处理接口
               path.equals("/");                                       // 根路径
        
        // 4. 输出调试信息
        System.out.println("JWT Filter - 是否跳过: " + shouldSkip);
        
        // 5. 返回是否跳过的结果
        return shouldSkip;
    }
} 